package com.example.mq2.mqserver;

import com.example.mq2.common.Mq2Exception;
import com.example.mq2.mqserver.core.*;
import com.example.mq2.mqserver.datacenter.DiskDataCenter;
import com.example.mq2.mqserver.datacenter.MemoryDataCenter;
import sun.java2d.windows.GDIWindowSurfaceData;

import java.io.IOException;
import java.util.Map;

/*
* 这个类表示虚拟主机，每个虚拟主机下面都会管理：交换机，队列，绑定
* 同时提供api供上层调用
* 针对 VirtualHost 这个类，作为业务逻辑的整合者，就需要对代码中的抛出的异常进行处理了*/
public class VirtualHost {
    private String virtualHostName;
    private MemoryDataCenter memoryDataCenter = new MemoryDataCenter();
    private DiskDataCenter diskDataCenter = new DiskDataCenter();
    private Router router = new Router();

    // 使用这个锁来作为操作交换机的锁对象
    private final Object exchangeLocker = new Object();
    // 使用这个锁来作为操作队列的锁对象
    private final Object queueLocker = new Object();

    public String getVirtualHostName() {
        return virtualHostName;
    }

    public MemoryDataCenter getMemoryDataCenter() {
        return memoryDataCenter;
    }

    public DiskDataCenter getDiskDataCenter() {
        return diskDataCenter;
    }

    // 针对这个类进行初始化
    // 此时的初始化需要将硬盘中的数据进行初始化，完成建库建表操作
    // 但是对于内存中的数据是不需要初始化的，只要把这个类 new 出来即可
    public VirtualHost(String name) {
        this.virtualHostName = name;

        // 对于管理内存这个类来说，各种存储用的数据结构都已经创建好实例了，只要把这个类 new 出来即可
        diskDataCenter.init();
        // 另外需要将硬盘上的数据恢复到内存中
        // 如果是首次启动，恢复数据这个操作没啥用，但是如果服务器如果已经运行一段时间了，此时恢复数据到内存中
        // 这个操作是很有必要的
        try {
            memoryDataCenter.recovery(diskDataCenter);
        } catch (Mq2Exception | IOException | ClassNotFoundException e) {
            e.printStackTrace();
            System.out.println("[VirtualHostName] 恢复内存数据失败! ");
        }
    }

    // 创建交换机
    // 如果交换机不存在，将创建，如果存在，就直接返回
    // 返回值是 boolean，创建成功 返回 true， 创建失败返回 false
    public boolean exchangeDeclare(String exchangeName, ExchangeType exchangeType, boolean durable, boolean autoDelete,
                                   Map<String, Object> arguments) {
        // 把交换机的名字 加上虚拟主机作为前缀
        // 就是已进入这个方法，就是二者结合的名字，也就是调用这个方法的人只关心交换机的原来的名字即可
        exchangeName = virtualHostName + exchangeName;
        try {
            synchronized (exchangeLocker) {
                // 1. 判断交换机是否已经存在，直接通过内存查询
                Exchange existsExchange = memoryDataCenter.getExchange(exchangeName);
                if (existsExchange != null) {
                    System.out.println("[VirtualHost] 交换机已经存在！exchangeName= " + exchangeName);
                    // 这样也算是创建成功了
                    return true;
                }
                // 2. 真正创建交换机，先构造 Exchange 对象
                Exchange exchange = new Exchange();
                exchange.setName(exchangeName);
                exchange.setExchangeType(exchangeType);
                exchange.setDurable(durable);
                exchange.setAutoDelete(autoDelete);
                exchange.setArguments(arguments);

                // 下面这块的逻辑是先插入硬盘，然后再插入内存，插入硬盘是比较难的，会涉及到数据文件可能会满了之类的
                // 如果插入硬盘失败了，此时就抛异常了，就不再去插入内存了，但是如果先插入内存，如果插入硬盘失败了
                // 此时还需要把内存中的数据删除掉
                // 3. 把交换机对象写入硬盘
                if (durable) {
                    diskDataCenter.insertExchange(exchange);
                }
                // 4. 把交换机对象写入内存
                memoryDataCenter.insertExchange(exchange);
                System.out.println("[VirtualHost] 交换机创建完成！exchangeName= " + exchangeName);
            }
            return true;
        } catch (Exception e) {
            System.out.println("[VirtualHost] 交换机创建失败！exchangeName= " + exchangeName);
            e.printStackTrace();
            return false;
        }
    }

    // 删除交换机
    public boolean exchangeDelete(String exchangeName) {
        exchangeName = virtualHostName + exchangeName;
        try {
            synchronized (exchangeLocker) {
                // 1. 先找到要删除的交换机
                Exchange toDelete = memoryDataCenter.getExchange(exchangeName);
                if (toDelete == null) {
                    throw new Mq2Exception("[VirtualHost] 交换机不存在无法删除！");
                }
                // 2. 删除硬盘上的数据
                if (toDelete.isDurable()) {
                    diskDataCenter.deleteExchange(exchangeName);
                }
                // 3. 删除内存中的交换机
                memoryDataCenter.deleteExchange(exchangeName);
                System.out.println("[VirtualHost] 交换机删除成功！exchangeName= " + exchangeName);
            }
            return true;
        } catch (Exception e) {
            System.out.println("[VirtualHost] 交换机删除失败！exchangeName= " + exchangeName);
            e.printStackTrace();
            return false;
        }
    }

    // 创建队列
     public boolean queueDeclare(String queueName, boolean durable, boolean exclusive, boolean autoDelete,
                                 Map<String, Object> arguments) {
        // 将队列的名字，给拼接上虚拟主机的名字
         queueName = virtualHostName + queueName;
         try {
             synchronized (queueLocker) {
                 // 1. 判定队列是否存在
                 MSGQueue existsQueue = memoryDataCenter.getQueue(queueName);
                 if (existsQueue != null) {
                     System.out.println("[VirtualHost] 队列已经存在！queueName= " + queueName);
                     return true;
                 }
                 // 2. 创建队列对象
                 MSGQueue queue = new MSGQueue();
                 queue.setName(queueName);
                 queue.setDurable(durable);
                 queue.setExclusive(exclusive);
                 queue.isAutoDelete(autoDelete);
                 queue.setArguments(arguments);
                 // 3. 写硬盘
                 if (durable) {
                     diskDataCenter.insertQueue(queue);
                 }
                 // 4.写内存
                 memoryDataCenter.insertQueue(queue);
                 System.out.println("[ VirtualHost] 队列创建成功！queueName= " + queueName);
             }
             return true;
         } catch (Exception e) {
             System.out.println("[VirtualHost] 队列创建失败！ queueName= " + queueName);
             e.printStackTrace();
             return false;
         }
     }

     // 删除队列
    public boolean queueDelete(String queueName) {
        queueName = virtualHostName + queueName;
        try {
            synchronized (queueLocker) {
                // 1. 根据队列的名字，查询下当前队列的对象
                MSGQueue queue = memoryDataCenter.getQueue(queueName);
                if (queue == null) {
                    throw new Mq2Exception("[VirtualHost] 队列不存在！无法删除 queueName= " + queueName);
                }
                // 2. 删除硬盘中的队列
                if (queue.isDurable()) {
                    diskDataCenter.deleteQueue(queueName);
                }
                // 3. 删除内存中的队列
                memoryDataCenter.deleteQueue(queueName);
                System.out.println("[VirtualHost] 删除队列成功！queueName= " + queueName);
            }
            return true;
        } catch (Exception e) {
            System.out.println("[VirtualHost] 删除队列失败！queueName= " + queueName);
            e.printStackTrace();
            return false;
        }
    }

    // 创建绑定
    public boolean queueBind(String queueName, String exchangeName, String bindingKey) {
        queueName = virtualHostName + queueName;
        exchangeName = virtualHostName + exchangeName;
        try {
            // 因为此时的交换机和队列操作都和创建绑定相关联，所以此时就拿两把锁来同时对创建绑定这个操作来加锁
            // 同时还要注意加锁的顺序，不能加锁的顺序是颠倒的，如果加锁的顺序颠倒了，此时就会死锁
            synchronized (exchangeLocker) {
                synchronized (queueLocker) {
                    // 1. 判定当前的绑定是否已经存在了
                    Binding existBinding = memoryDataCenter.getBinding(exchangeName, queueName);
                    if (existBinding != null) {
                        throw new Mq2Exception("[VirtualHost] binding 已经存在！queueName= " + queueName +
                                ", exchanegName= " + exchangeName);
                    }
                    // 2. 检查bindingKey 是否合法
                    if (!router.checkBindingKey(bindingKey)) {
                        throw new Mq2Exception("[VirtualHost] binding 非法！bindingKey= " + bindingKey);
                    }
                    // 3.创建 Binding 对象
                    Binding binding = new Binding();
                    binding.setExchangeName(exchangeName);
                    binding.setQueueName(queueName);
                    binding.setBindingKey(bindingKey);
                    // 4. 获取一下对应的交换机和队列，如果交换机或者队列不存在，这样的绑定也是无法创建的
                    MSGQueue queue = memoryDataCenter.getQueue(queueName);
                    Exchange exchange = memoryDataCenter.getExchange(exchangeName);
                    if (queue == null) {
                        throw new Mq2Exception("[VirtualHost] 队列不存在！queueName= " + queueName);
                    }
                    if (exchange == null) {
                        throw new Mq2Exception("[VirtualHost] 交换机不存在！exchangeName= " + exchangeName);
                    }
                    // 如果上述的步骤都是没问题的，此时就可以将绑定保存到硬盘和内存中了
                    // 5. 先写硬盘
                    if (queue.isDurable() && exchange.isDurable()) {
                        diskDataCenter.insertBinding(binding);
                    }
                    // 6. 再写内存
                    memoryDataCenter.insertBinding(binding);
                    System.out.println("[VirtualHost] queueBind 成功！queueName= " + queueName +
                            ", exchangeName= " + exchangeName);
                }
            }
            return true;
        } catch (Exception e) {
            System.out.println("[VirtualHost] queueBind 失败！queueName= " + queueName +
                    ", exchangeName= " + exchangeName);
            e.printStackTrace();
            return false;
        }
    }

    // 删除绑定
    public boolean queueUnbind(String queueName, String exchangeName) {
        queueName = virtualHostName + queueName;
        exchangeName = virtualHostName + exchangeName;
        try {
            // 1. 获取 binding 看是否已经存在
            Binding binding = memoryDataCenter.getBinding(exchangeName, queueName);
            if (binding == null) {
                throw new Mq2Exception("[VirtualHost] 删除绑定失败！绑定不存在！exchangeName= " + exchangeName +
                        ", queueName= " + queueName);
            }
            MSGQueue queue = memoryDataCenter.getQueue(queueName);
            if (queue == null) {
                throw new Mq2Exception("[VirtualHost] 对应的队列不存在！queueName= " + queueName);
            }
            Exchange exchange = memoryDataCenter.getExchange(exchangeName);
            if (exchange == null) {
                throw new Mq2Exception("[VirtualHost] 对应的交换机不存在！exchangeName= " + exchangeName);
            }
            // 如果上述步骤都没问题的话就删除硬盘的绑定
            diskDataCenter.deleteBinding(binding);
            // 删除内存中的绑定
            memoryDataCenter.deleteBinding(binding);
            System.out.println("[VirtualHost] 删除绑定成功！");
            return true;
        } catch (Exception e) {
            System.out.println("[VirtualHost] 删除绑定失败");
            e.printStackTrace();
            return false;
        }
    }

    // 发送消息到指定的队列中
    public boolean basicPublish(String exchangeName, String routingKey, BasicProperties basicProperties, byte[] body) {
        try {
            // 1. 转换交换机的名字

            // 2. 检查 routingKey 是否是合法的


        }
    }
}
